今天要講的 Event Loop,看到網路上很多文章都有提到,這是 JS 最獨特的地方,幾乎沒有其他語言有這個特性。
今天這份文章會以Philip Roberts 在 JS Conf 的演講影片 What the heck is the event loop anyway,為主要架構,在搭配其他文章內容,如有說錯還請大家指教!
之所以選這部影片,是因為我第一次接觸 Event Loop就是看這支影片,因為當時AC的作業要寫這篇的心得並發到部落格,當時真的有看沒有懂XDD
"JavaScript 是一種單線程(single-threaded)的程式語言",這表示 JavaScript 在執行時只使用單一的執行緒。這代表它一次只能執行一段程式碼,無法同時處理多個任務。
然而,JavaScript 通常是在瀏覽器或 Node.js 這樣的環境中運行。這些環境提供了一個執行環境(Runtime Environment),包含了其他執行緒,這些執行緒可以處理一些非同步的任務。比如說,當你使用 setTimeout
函式時,JavaScript 引擎會將它交給執行環境的其他部分處理,以便在指定的時間後執行你提供的code。
簡單來說,雖然 JavaScript 自身是單線程的,但是在運行的環境中(如瀏覽器或 Node.js),可以利用其他執行緒來處理非同步的任務,從而實現同時進行多項操作。
還記得我們前幾章一直有提到的execution context
嗎?
當 JavaScript 程式開始執行時,它會創建一個global execution context
(全域執行環境)。在這個global execution context
內,如果呼叫了一個函式,就會為該函式創建一個新的execution context
,並將其添加到"執行環境堆疊" 的頂部。當函式完成時,它的execution context
從堆疊中移除,程式將繼續在在呼叫函式之前的環境中執行。
每個執行環境都有自己的變數和函式宣告空間。這個執行環境堆疊的概念是管理程式範圍和執行順序的關鍵。
由於部分原因無法觀看節錄的畫面,關於執行堆疊(called stack)的片段大家可以到4分40左右開始觀看。
下面是我節錄影片裡,關於執行堆疊(called stack)的片段,相信大家會更了解。
這段影片有提到一個詞,Blocking,中文稱作『阻塞』。
他的意思是當我們在執行程式遇到 blocking時,程式的執行就會像卡住一樣等待很久的時間,直到 blocking的操作完成後程式才會繼續執行。
由於部分原因無法觀看節錄的畫面,這部分大家可以到8分10秒左右觀看
這樣會造成用戶體驗非常糟糕,因此JavaScript 鼓勵使用 non-blocking(非阻塞) 的操作,如asynchronous(非同步)編程模型,例如使用 Promise、async/await 或 callbacks 來處理潛在的長時間操作,以保證程式在等待某些操作完成時仍然可以繼續執行其他任務,不會被blocking。
了解了什麼是blocking(阻塞)與non-blocking(非阻塞)後,其實你已經懂了synchronous(同步) 與 asynchronous(非同步),為什麼這麼說呢?因為在Node.js 的官方文件是這麼說的:
Blocking methods execute synchronously and non-blocking methods execute asynchronously.
阻塞的方法會同步地(synchronously)執行,而非阻塞的方法會非同步地(asynchronously)執行
但我相信還是會有很多人跟我一樣,同步不就是同時進行嗎?為什麼我卻只能一次執行一件事。
這就要提到中文翻譯的問題了,但我英文很差這邊就用另一個方式跟大家解釋:
Synchronous:餐廳老闆自己從帶位、點餐、煮菜等,一條龍完成所有作業,在完成一組客人後,才能接待下一位客人。
在JavaScript大部分功能都是同步的,而同步的定義是指:程式碼的執行順序,依照由上而下執行,最後才輸出。
優點當然是很好閱讀,但缺點就是效率變很差。
Asynchronous:餐廳老闆請了不同的人負責各自專門的事情,服務生負責點餐、廚師負責煮飯。在固定的時間內,可以藉由大家的合作,處理多組客人。
在這案例我們可以得知:
我們可以做個總結:所謂的「非同步處理」,可以說就是釐清各式各樣的「等待」流程,並且在因「等待」而複雜化的程式流程中,確保程式能正確運作。
詳細的同步非同步我們會在下一篇中跟大家解釋。
在了解同步非同步後,我們回到影片的案例。
在影片裡,演講者用了經典的setTimeout
來舉例,讓我們看一下code:
console.log('hi')
setTimeout(function () {
console.log('there')
}, 5000)
console.log('JSConfEU')
這段code的結果想必大家應該都很清楚,就是:
hi
JSConfEU
there
但是如果我們把setTimeout
的時間改成0的時候,結果會是什麼呢?
答案還是:
hi
JSConfEU
there
疑?為什麼setTimeout
的時間已經是0了應該會直接執行啊?
這就是我們今天所要探討的主題:Event Loop!
我們先來看一張圖:
我們前面有提到,JS 引擎底下有三個部分:
但整個瀏覽器並不只是只有一個JS引擎所組成,因為 JS 語言特性屬於單執行緒,同時又為了讓網頁具有像「監聽事件」、「計時」、「 拉第三方API 」這些類似「背景作業」的功能,瀏覽器提供了另外一些部分來達成,分別是:
整個由上述部分,包含 JS 引擎所組成的環境,也稱為 JS Runtime Environment ( JRE )。
我們先來看一下演講者是如何解釋這整個運作,之後我們再來解釋這4個名詞。
備注:setTimeout 5000的解說可看12:50-14:50,整個流程比較詳細
Web API 是瀏覽器提供的一組介面,讓 JavaScript 可以與瀏覽器進行互動。
剛剛我們知道了瀏覽器並不只是只有一個JS引擎所組成,我們在寫網頁的時候,有一些所謂「內建的」API 如 SetTimeout / setInterval ,這些 API 不存在於 JavaScript 原始碼內,但你仍然可以在開發時直接使用。
因為這些 API 是屬於瀏覽器提供的 Web API 。Web API 並非 JS 引擎的一部分,但他屬於瀏覽器運行流程的一環。
關於 Web API ,舉一些例子:
這類 Web API 在與 JS 原始碼一起執行的時候,並不會直接影響 JS 主執行環境的運行,而是會運用非同步的方式,瀏覽器會將這些必須等待執行結果的動作,丟給其他部分去執行,讓 JS 引擎可以繼續做他應該做的事情,等到Web API 完成後將相應的回調函式添加到Event Queue中。
在 JavaScript 中,Event Queue 是用來處理非同步事件的一個概念。
當非同步事件(如定時器、DOM 事件、HTTP 請求的回應等)發生時,相關的回調函式會被放入Event Queue中,等待被執行。Event Queue是一個先進先出(FIFO)的資料結構,確保了事件的執行順序。
影片中並沒有提到這個,但在<<JS 原力覺醒 >>的鐵人賽文章有提到這個。以下是原文:
Event Table 與 Event Queue 互相搭配的資料集合,他負責記錄在非同步目的達成後,有哪些函式或者事件要被執行,這裡指的非同步目的指的是像計時完畢、API資料獲取完畢、事件被觸發。當我們執行 setTimeout 這個函式時,JS 會把給定的函式與像是倒數的秒數之類的附帶資訊 ( meta data )推送到 Event Table裡面,等到一秒過後(目的達成)該函式就會被正式推送到Event Queue 等待執行。
Event Loop 是一個持續運行的機制,用於監聽 Event Queue 中是否有待執行的回調函式。如果Event Queue 不為空,事件迴圈會將隊列中的回調函式逐一取出執行。Event Loop 確保了 JavaScript 程序在等待非同步操作的同時,主線程仍然可以處理其他任務。
到這邊大家應該都懂了吧,如果還是不是那麼清楚,我們在把下面這段看完應該就更了解了。
第一次嘗試用影片來解說,不然這個要解釋流程我又要劃一堆圖了。
如果有違反任何規定請告知我,我會馬上處理!
參考資料:
http://www.ruanyifeng.com/blog/2014/10/event-loop.html
https://yeefun.github.io/event-loop-in-depth/
What the heck is the event loop anyway